<!DOCTYPE html>
<html>

<head>
  <script src="https://unpkg.com/konva@5.0.0/konva.js"></script>
  <script src="https://unpkg.com/jquery"></script>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
  <meta charset="utf-8" />
  <title>Flowchart Demo</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
      background-color: #f0f0f0;
    }

    #container {
      padding: 20px;
    }
  </style>
</head>

<body>
  <div id="container"></div>
  <script>
    (function (global, factory) {
      "use strict";
      if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = global.document ?
          factory(global, true) :
          function (w) {
            if (!w.document) {
              throw new Error("flowchart requires a window with a document");
            }
            return factory(w);
          };
      } else {
        factory(global);
      }
    })(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
      "use strict";

      var POINT_NAME = "point";
      var POINT_COLOR = "#f6f7ff";
      var POINT_ACTIVITE_COLOR = "#d9230f";
      var DEBUG = true;
      var idCounter = 1;

      var NODE = {
        name: "group", // konva name
        type: "",
        color: "#f6f7ff", // konva fill
        borderColor: "#03689a", // konva stroke
        text: "",
        width: 200,
        height: 35,
        fontSize: 16,
        padding: 10,
        fontSize: 16,
        align: "center",
        fill: 'black',
        draggable: true,
        onClick: function (event, node) {
          if (DEBUG) console.log(`onClick `, arguments)
        },
        onDblclick: function (event, node) {
          if (DEBUG) console.log(`onDblclick `, arguments)
        },
        onDragstart: function (event, node) {
          if (DEBUG) console.log(`onDragstart `, arguments)
        },
        onDragmove: function (event, node) {
          if (DEBUG) console.log(`onDragmove `, arguments)
        },
        onDragend: function (event, node) {
          if (DEBUG) console.log(`onDragend `, arguments)
        },
        onSelected: function (event, node) {
          if (DEBUG) console.log(`onSelected `, arguments)
        },
      };

      var DEFAULT_NODE_TYPE_LIST = [{
          "type": "start",
          "text": "模型输入",
          color: "#ebf9dc"
        },
        {
          "type": "service",
          "text": "内部服务"
        },
        {
          "type": "end",
          "text": "模型输出",
          color: "#701321c2"
        },
      ];

      var DEFAULT_OPTIONS = {
        container: "",
        name: "container",
        width: window.innerWidth,
        height: window.innerHeight,
        onArrowClick: function (event, node) {
          if (DEBUG) console.log(`onArrowClick `, arguments)
        },
        onArrowDblclick: function (event, node) {
          if (DEBUG) console.log(`onArrowDblclick `, arguments)
        },
        panel: {
          name: "panel",
          draggable: false,
          width: 250,
          height: 300,
          nodes: []
        }, // 面板节点
      }

      var Flowchart = function (options) {
        this._options = $.extend(true, {}, DEFAULT_OPTIONS, options);
        if (!this._options.panel.nodes || this._options.panel.nodes.length == 0) {
          this._options.panel.nodes = DEFAULT_NODE_TYPE_LIST.map(function (val) {
            return $.extend({}, NODE, val)
          });
        } else {
          this._options.panel.nodes = this._options.panel.nodes.map(function (val) {
            return $.extend({}, NODE, val)
          });
        }

        this.init();
      }

      // 开始初始化流程
      Flowchart.prototype.init = function () {
        this.container = {};
        var container = this.container;
        container.stage = new Konva.Stage({
          container: this._options.container,
          width: this._options.width,
          height: this._options.height
        });
        container.layer = new Konva.Layer();

        let paddingRight = 50;
        container.panel = new Konva.Rect({
          x: this._options.width - this._options.panel.width - paddingRight,
          y: 30,
          width: this._options.panel.width,
          height: this._options.panel.height,
          fill: '#ffffff',
          stroke: '#ffe187',
          strokeWidth: 2
        });
        container.layer.add(container.panel);

        // 添加panel 面板
        let startX = this._options.width - this._options.panel.width - paddingRight / 2;
        let startY = 30 + 20;
        this._options.panel.nodes.forEach((node) => {
          let el = new Konva.Rect({
            name: "el",
            width: node.width,
            height: node.height,
            fill: node.color || '#ffffff',
            stroke: node.borderColor,
            strokeWidth: 2
          });
          let text = new Konva.Text({
            name: "nodeText",
            width: node.width,
            height: node.height,
            text: node.text,
            padding: node.padding || 10,
            fontSize: node.fontSize,
            align: node.align,
            fill: node.fill
          });
          let group = new Konva.Group({
            x: startX,
            y: startY,
            draggable: node.draggable,
            name: node.name
          });
          // 初始化事件监听
          group.on("dragstart", this.dragstartHandler.bind(this));
          group.on("dragend", evt => container.stage.container().style.cursor = 'default');
          // 保存节点参数到konva对象中
          group.$data = $.extend({}, node);
          // 添加到布局中
          group.add(el);
          group.add(text);
          container.layer.add(group);
          startY = startY + node.height + 20;
        });

        container.tr = new Konva.Transformer({
          resizeEnabled: false,
          rotateEnabled: false,
          borderStrokeWidth: 4
        });
        container.layer.add(container.tr);
        container.stage.add(container.layer);
        container.stage.on("mousemove", this.updateCombineArrow.bind(this));
        container.stage.on('click tap dragstart', this.transformerClickHandler.bind(this));
        // 添加取消事件处理
        $(window).keyup((e) => {
          if (e.keyCode === 27) {
            this.cancelCombineArrow.apply(this);
          } else {
            return;
          }
          e.preventDefault();
          container.layer.draw();
        });

      }

      Flowchart.prototype.transformerClickHandler = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let tr = container.tr;

        if (evt.target === stage) {
          tr.nodes([]);
          layer.draw();
          return;
        }
        let target = evt.target;
        if (target.getClassName() == "Arrow") {
          // call arrow onSelected event handler
          return;
        }

        if (target.hasName(POINT_NAME)) {
          return;
        }

        // target on group
        while (target.getClassName() != "Group") {
          target = target.getParent();
          if (!target) {
            return;
          }
        }
        tr.nodes([target]);
        // call node onSelected event
        if (target.$data.onSelected) {
          target.$data.onSelected(evt, target.$data, target, this);
        }
        // tr.moveToBottom();
        layer.draw();
      }

      Flowchart.prototype.dragstartHandler = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let group = evt.target;
        // 鼠标样式
        stage.container().style.cursor = 'move';
        // 克隆一个新对象 停留在原处，给下一次拖拽新增
        let newGroup = group.clone();
        newGroup.$data = $.extend({}, group.$data);
        layer.add(newGroup);

        group.id(idCounter++); // 设置id
        // 初始化事件监听
        this.initGroupEvent(group);
        // refresh 
        layer.draw();
      }

      Flowchart.prototype.initGroupEvent = function(group) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer
        let node = group.$data;

        group.off("dragstart");
        group.on("click", (event) => {
          node.onClick(event, group, node)
        });
        group.on("dblclick", (event) => {
          node.onDblclick(event, group, node)
        });
        group.on("dragstart", (event) => {
          stage.container().style.cursor = 'move';
          node.onDragstart(event, group, node)
        });
        group.on("dragmove", (event) => {
          this.updateCombineDoneByDrag.call(this, event, group)
        });
        group.on("dragmove", (event) => {
          node.onDragmove(event, group, node)
        });
        group.on("dragend", (event) => {
          node.onDragend(event, group, node)
        });

        let elH = group.$data.height;
        let elW = group.$data.width;
        let self = this;
        group.on("mouseover", function (e) {
          stage.container().style.cursor = 'move';
          let mp = stage.getPointerPosition();
          let tp = e.target.absolutePosition();
          let h = elH;
          let w = elW;
          // 判断是在上还是在下
          let pointConfig = {
            name: POINT_NAME,
            radius: 7,
            fill: POINT_COLOR,
            stroke: '#03689a',
            strokeWidth: 2,
          }
          let points = group.find(".point");
          if (!points || points.toArray().length == 0) {
            let t = new Konva.Circle($.extend({
              id: idCounter++,
              x: w / 2,
              y: 0
            }, pointConfig));
            t._positionType = "t";
            let b = new Konva.Circle($.extend({
              id: idCounter++,
              x: w / 2,
              y: h
            }, pointConfig));
            b._positionType = "b";
            let l = new Konva.Circle($.extend({
              id: idCounter++,
              x: 0,
              y: h / 2
            }, pointConfig));
            l._positionType = "l";
            let r = new Konva.Circle($.extend({
              id: idCounter++,
              x: w,
              y: h / 2
            }, pointConfig));
            r._positionType = "r";
            //point.on("click tap", lineClickHandler);
            let ps = [t, b, l, r];
            group.add.apply(group, ps);
            ps.forEach(p => p.on("click tap", self.pointClick.bind(self)));
          } else {
            points.each(p => p.show());
          }
          // refresh 
          layer.draw();
        });
        group.on("mouseout", function (evt) {
          stage.container().style.cursor = 'default';
          self.refreshGroupPoints(group, false);
          // refresh 
          layer.draw();
        });
      }

      Flowchart.prototype.getGroupActivePoint = function (group) {
        let active;
        let points = group.find(".point");
        for (let i = 0; i < points.length; i++) {
          let p = points[i];
          if (p.activeFlag) {
            active = p;
            break;
          }
        }
        return active;
      }

      // 拖拽group时， 将连接也联动拖拽
      Flowchart.prototype.updateCombineDoneByDrag = function (evt, group) {
        let activePoint = this.getGroupActivePoint(group);
        let container = this.container;
        let combineDone = container.combineDone;

        if (combineDone) {
          combineDone.forEach(combine => {
            if (combine[0] === group || combine[2] === group) {
              let headGroup = combine[0];
              let tailGroup = combine[2];
              // 原点
              let originX = headGroup.absolutePosition().x + headGroup.width() / 2;
              let originY = headGroup.absolutePosition().y + headGroup.height() / 2;
              // 对点
              let targetX = tailGroup.absolutePosition().x + tailGroup.width() / 2;
              let targetY = tailGroup.absolutePosition().y + tailGroup.height() / 2;

              let arrow = combine[4];
              let sin45 = Math.sin(Math.PI / 180 * 30);

              let x = targetX - originX;
              let y = targetY - originY;
              let r = Math.sqrt(x * x + y * y);
              /**
               *        |
               *    c   |   b
               *        |
               * ---------------
               *        |
               *    d   |   a
               *        |
               * 
               */
              let ps = arrow.points();
              let position, tposition;
              let sin = Math.abs(y / r);
              if (x > 0 && y > 0) {
                // position a
                position = sin > sin45 ? "b" : "r";
                tposition = sin > sin45 ? "t" : "l";
              } else if (x > 0 && y < 0) {
                // position b
                position = sin > sin45 ? "t" : "r";
                tposition = sin > sin45 ? "b" : "l";
              } else if (x < 0 && y < 0) {
                // position c
                position = sin > sin45 ? "t" : "l";
                tposition = sin > sin45 ? "b" : "r";
              } else if (x < 0 && y > 0) {
                // position d
                position = sin > sin45 ? "b" : "l";
                tposition = sin > sin45 ? "t" : "r";
              }
              if (position) {
                let opoints = headGroup.find(".point");
                let oCircle, tCircle;
                for (let i = 0; i < opoints.length; i++) {
                  if (opoints[i]._positionType == position) {
                    oCircle = opoints[i];
                    break;
                  }
                }
                combine[1] = oCircle;
                let tpoints = tailGroup.find(".point");
                for (let i = 0; i < tpoints.length; i++) {
                  if (tpoints[i]._positionType == tposition) {
                    tCircle = tpoints[i];
                    break;
                  }
                }
                combine[3] = tCircle;
                let offset = this.calcOffset(oCircle.absolutePosition().x, oCircle.absolutePosition().y,
                  tCircle.absolutePosition().x, tCircle.absolutePosition().y, 7);
                ps[0] = oCircle.absolutePosition().x + offset[0];
                ps[1] = oCircle.absolutePosition().y + offset[1];
                ps[2] = tCircle.absolutePosition().x - offset[0];
                ps[3] = tCircle.absolutePosition().y - offset[1];
                //console.log(`ps ${ps}, origin ${position}, target ${tposition} sin ${sin} sin45 ${sin45} asin ${Math.asin(sin)/(Math.PI / 180)}`);
              }
              arrow.points(ps);
            }
          });
        }
      }

      // 计算偏移 coordinate
      Flowchart.prototype.calcOffset = function (ox, oy, tx, ty, offset) {
        let x = tx - ox;
        let y = ty - oy;
        let r = Math.sqrt(x * x + y * y);
        let sin = y / r;
        let cos = x / r;
        let r1 = r - offset;
        let x1 = x - cos * r1;
        let y1 = y - sin * r1;
        return [x1, y1];
      }

      // 刷新绘制过程的箭头 随鼠标动
      Flowchart.prototype.updateCombineArrow = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let combine = container.combine;
        if (combine) {
          let mp = stage.getPointerPosition();
          let tp = combine[1].absolutePosition();
          let arrowps = combine[4].points();
          // 原点
          let originX = arrowps[0];
          let originY = arrowps[1];
          let offset = this.calcOffset(tp.x, tp.y, mp.x, mp.y, 7);
          arrowps[0] = tp.x + offset[0];
          arrowps[1] = tp.y + offset[1];
          arrowps[2] = mp.x - offset[0];
          arrowps[3] = mp.y - offset[1];
          combine[4].points(arrowps);
          layer.draw();
        }
      }
      // 取消取连接 e.keyCode === 27 ESC or dblclick
      Flowchart.prototype.cancelCombineArrow = function (evt) {
        let container = this.container;
        let combine = container.combine;
        if (combine && combine[2] == null) {
          this.togglePointActive(combine[1], false);
          let arrow = combine[4];
          arrow.hide();
          arrow.destroy();
          container.combine = null;
        }
      }

      // 更新点的激活状态
      Flowchart.prototype.togglePointActive = function (point, activeFlag) {
        if (point) {
          if (typeof activeFlag === "undefined") {
            point.activeFlag = !point.activeFlag;
          } else {
            point.activeFlag = activeFlag;
          }
          point.fill(point.activeFlag ? POINT_ACTIVITE_COLOR : POINT_COLOR);
        }
        return point;
      }

      // 更新点的
      Flowchart.prototype.refreshGroupPoints = function (group, show) {
        if (group.children) {
          let points = group.find(".point");
          let hasActive = false;
          points.each(p => {
            if (p.activeFlag) {
              hasActive = true;
            }
          });
          if (points && !hasActive) points.each(p => p[show ? "show" : "hide"]());
        }
      }

      // point 点击连线
      Flowchart.prototype.pointClick = function (evt) {
        let container = this.container;
        let stage = container.stage;
        let layer = container.layer;
        let point = evt.target;
        if (point.hasName(POINT_NAME)) {
          let group = point.getParent();
          this.togglePointActive(point);
          // 清除其它的激活状态
          let points = group.find(".point");
          if (points) {
            points.each(p => {
              if (!(p === point)) {
                this.togglePointActive(p, false);
              }
            });
          }
          // 连接处理部分
          if (point.activeFlag) {

            let mp = stage.getPointerPosition();
            let tp = point.absolutePosition();
            // 判断是否当前有头部对象 没有则初始化 初始后直接返回 等待用户选择下一个点
            if (!container.combine) {
              // 记录当前连接的对象   头    头的点    尾     尾的点  箭头
              container.combine = [group, point, null, null, null];
              let offset = this.calcOffset(tp.x, tp.y, mp.x, mp.y, 7);
              // 绘制连线 
              let arrow = new Konva.Arrow({
                x: 0,
                y: 0,
                points: [tp.x, tp.y, mp.x - offset[0], mp.y - offset[1]],
                pointerLength: 15,
                pointerWidth: 15,
                fill: '#808080',
                stroke: '#808080',
                strokeWidth: 3
              });
              container.combine[4] = arrow;
              // 判断初始化连接完成的集合
              if (!container.combineDone) {
                container.combineDone = [];
              }
              layer.add(arrow);
              layer.draw();
              return;
            }

            // 用户选择了下一个点， 进行两点连线
            let combine = container.combine;
            if (group === combine[0]) {
              // 不能自己连自己
              return;
            }
            container.combine = null; // 释放当前连接  给下一次让位

            combine[2] = group;
            combine[3] = point;
            // 推入完成集合中
            container.combineDone.push(combine);
            let arrowps = combine[4].points();
            let offset = this.calcOffset(arrowps[0], arrowps[1], tp.x, tp.y, 7);
            arrowps[2] = tp.x - offset[0];
            arrowps[3] = tp.y - offset[1];
            this.togglePointActive(combine[1], false);
            this.togglePointActive(combine[3], false);
            this.refreshGroupPoints(combine[0], false);
            // 绑定事件
            combine[4].on("click tap", (event) => {
              this._options.onArrowClick(event, combine[0].$data, combine[2].$data);
            });
            combine[4].on("dblclick dbltap", (event) => {
              this._options.onArrowDblclick(event, combine[0].$data, combine[2].$data);
            });
            combine[4].id(idCounter++); // 设置id
            layer.draw();
            return;
          }

          layer.draw();
        }
      }

      // 加载流程图
      Flowchart.prototype.load = function(data = {}) {
        let flowNodes = data.nodes || [];
        let links = data.links || [];
        let container = this.container;
        let layer = container.layer;
        let maxId = idCounter;
        // 判断初始化连接完成的集合
        if (!container.combineDone) {
          container.combineDone = [];
        }

        let panelNodes = this._options.panel.nodes;
        let nodeMap = {};
        panelNodes.forEach( node => nodeMap[node.type] = node);

        flowNodes.forEach( node => {
          let obj = Konva.Node.create(node);
          let $$data = nodeMap[node.$data.type];
          obj.$data = $.extend({}, $$data, node.$data);
          let points = obj.find(".point");
          points.forEach( child => {
            // 统计最大id
            if (maxId < child.id()) {
              maxId = child.id();
            }
            for (let i=0; i<node.children.length; i++) {
              let point = node.children[i];
              if (child.id() == point.attrs.id*1 && point.className === "Circle") {
                child._positionType = point._positionType;
                break;
              }
            }
            // 点的事件初始化
            child.on("click tap", this.pointClick.bind(this))
          });
          // 初始化事件监听
          this.initGroupEvent(obj);
          // 统计最大id
          if (maxId < node.attrs.id*1) {
            maxId = node.attrs.id*1;
          }
          layer.add(obj);
        });
        links.forEach( link => {
          let arrow = Konva.Node.create(link);
          let connect = link.connect;
          let combine = [];
          // 找头找尾找到点
          layer.children.forEach( el => {
            if (el.id() === connect[0]*1) {
              combine[0] = el;
              let points = el.find(".point");
              for (let i=0 ; i<points.length; i++) {
                if (points[i]._positionType == connect[1]) {
                  combine[1] = points[i];
                  break;
                }
              }
            } else if (el.id() === connect[2]*1) {
              combine[2] = el;
              let points = el.find(".point");
              for (let i=0 ; i<points.length; i++) {
                if (points[i]._positionType == connect[3]) {
                  combine[3] = points[i];
                  break;
                }
              }
            }
          });
          combine[4] = arrow;
          layer.add(arrow);
          container.combineDone.push(combine);
          // 初始化事件监听
          arrow.on("click tap", (event) => {
            this._options.onArrowClick(event, combine[0].$data, combine[2].$data);
          });
          arrow.on("dblclick dbltap", (event) => {
            this._options.onArrowDblclick(event, combine[0].$data, combine[2].$data);
          });
          // 统计最大id
          if (maxId < link.attrs.id*1) {
            maxId = link.attrs.id*1;
          }
        });
        // 刷新画布
        layer.draw();
        idCounter = ++maxId;
      }
      // 导出流程图
      Flowchart.prototype.export = function() {
        // 面板节点信息
        let panelNodes = this._options.panel.nodes;
        let nodeMap = {};
        panelNodes.forEach( node => nodeMap[node.type] = node);
        let container = this.container;
        let layer = container.layer;

        let flowNodes = [], links;
        layer.children.forEach( child => {
          if (child.getClassName() == "Group" && !!nodeMap[child.$data.type]) {
            let nodeObj = child.toObject();
            nodeObj.$data = child.$data;
            let points = child.find(".point");
            if (!points || points.length == 0 ) {
              return;
            }
            // 记录点的类型
            nodeObj.children.forEach( child => {
              if (child.className === "Circle") {
                for (let i=0; i<points.length; i++) {
                  if (child.attrs.id == points[i].id()) {
                    child._positionType = points[i]._positionType;
                    break;
                  }
                }
              }
            });
            flowNodes.push(nodeObj);
          }
        });
        links = container.combineDone.map( link => {
          let linkObj = link[4].toObject();
          linkObj.connect = [link[0].id(), link[1]._positionType, link[2].id(), link[3]._positionType];
          return linkObj
        });
        return {
          nodes: flowNodes,
          links: links
        };
      }

      if (typeof noGlobal === "undefined") {
        window.Flowchart = Flowchart;
      }

      return Flowchart;
    })
  </script>
  <script>
    var flow = new Flowchart({
      container: "container",
      panel: {
        nodes: [{
          type: "start",
          text: "模型输入",
          color: "#f6f7ff", // konva fill
          borderColor: "#03689a", // konva stroke
          // onClick: function (event, node) {},
          // onDblclick: function (event, node) {},
          // onDragstart: function (event, node) {},
          // onDragmove: function (event, node) {},
          // onDragend: function (event, node) {},
          // onSelected: function (event, node) {}
        }, {
          type: "service",
          text: "内部服务",
          color: "#f6f7ff", // konva fill
          borderColor: "#03689a", // konva stroke
          // onClick: function (event, node) {},
          // onDblclick: function (event, node) {},
          // onDragstart: function (event, node) {},
          // onDragmove: function (event, node) {},
          // onDragend: function (event, node) {},
          // onSelected: function (event, node) {}
        }, {
          type: "end",
          text: "模型输出",
          color: "#f6f7ff", // konva fill
          borderColor: "#03689a", // konva stroke
          // onClick: function (event, node) {},
          // onDblclick: function (event, node) {},
          // onDragstart: function (event, node) {},
          // onDragmove: function (event, node) {},
          // onDragend: function (event, node) {},
          // onSelected: function (event, node) {}
        }, ]
      }
    });
    let data = JSON.parse('{"nodes":[{"attrs":{"x":768,"y":97,"draggable":true,"name":"group","id":1},"className":"Group","children":[{"attrs":{"name":"el","width":200,"height":35,"fill":"#f6f7ff","stroke":"#03689a"},"className":"Rect"},{"attrs":{"name":"nodeText","width":200,"height":35,"text":"模型输入","padding":10,"fontSize":16,"align":"center","fill":"black"},"className":"Text"},{"attrs":{"id":7,"x":100,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"t"},{"attrs":{"id":8,"x":100,"y":35,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"b"},{"attrs":{"id":9,"y":17.5,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"l"},{"attrs":{"id":10,"x":200,"y":17.5,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"r"}],"$data":{"name":"group","type":"start","color":"#f6f7ff","borderColor":"#03689a","text":"模型输入","width":200,"height":35,"fontSize":16,"padding":10,"align":"center","fill":"black","draggable":true}},{"attrs":{"x":905,"y":246,"draggable":true,"name":"group","id":2},"className":"Group","children":[{"attrs":{"name":"el","width":200,"height":35,"fill":"#f6f7ff","stroke":"#03689a"},"className":"Rect"},{"attrs":{"name":"nodeText","width":200,"height":35,"text":"内部服务","padding":10,"fontSize":16,"align":"center","fill":"black"},"className":"Text"},{"attrs":{"id":3,"x":100,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"t"},{"attrs":{"id":4,"x":100,"y":35,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"b"},{"attrs":{"id":5,"y":17.5,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"l"},{"attrs":{"id":6,"x":200,"y":17.5,"name":"point","radius":7,"fill":"#f6f7ff","stroke":"#03689a","visible":false},"className":"Circle","_positionType":"r"}],"$data":{"name":"group","type":"service","color":"#f6f7ff","borderColor":"#03689a","text":"内部服务","width":200,"height":35,"fontSize":16,"padding":10,"align":"center","fill":"black","draggable":true}}],"links":[{"attrs":{"points":[873.37437838348,136.48509275168118,999.6189721754681,241.52288714106805],"pointerLength":15,"pointerWidth":15,"fill":"#808080","stroke":"#808080","strokeWidth":3,"id":11},"className":"Arrow","connect":[1,"b",2,"t"]}]}');
    //flow.load(data);
  </script>
</body>

</html>